from __future__ import annotations

import typing as t
from enum import IntEnum

from app.common.cache import get_redis
from app.common.const import BLOCK_ID_SET_KEY_TEMPLATE
from app.common.const import FOLLOWEE_ID_SET_KEY_TEMPLATE
from app.common.const import USER_BLOCK_LIST_MAX_LEN
from app.common.const import USER_FOLLOWEE_MAX_LEN
from app.common.const import USER_LIKED_ARTICLE_ID_SET_KEY_TEMPLATE
from app.common.decorators import GetOrSetInCache
from app.common.errors import ApiError
from app.common.errors import Error
from app.lib.model import Model
from tortoise import fields


class AuthEnum(IntEnum):
    basic_auth = 0
    mobile_auth = 1
    wechat_auth = 2
    qq_auth = 3


class GenderEnum(IntEnum):
    female = 0
    male = 1
    unknown = 2


gender_dict = {GenderEnum.female: "女性", GenderEnum.male: "男性", GenderEnum.unknown: "未知"}

user_model_name = "models.User"
tag_model_name = "models.Tag"
permission_model_name = "models.Permission"


class User(Model):
    class Meta:
        table = "User"

    auto_id = fields.IntField(pk=True)
    phone_number = fields.CharField(max_length=100, null=True)
    description = fields.CharField(max_length=255)
    gender = fields.IntEnumField(
        GenderEnum, default=GenderEnum.unknown, description="性别:0女 1男 2未知"
    )
    username = fields.CharField(max_length=255, description="用户名", unique=True)
    nickname = fields.CharField(max_length=255, description="昵称", unique=True)
    password = fields.CharField(max_length=255)
    auth_type = fields.IntEnumField(
        AuthEnum, default=AuthEnum.basic_auth, description="登录策略"
    )
    avatar_url = fields.CharField(max_length=100, null=True, description="头像url")
    created_at = fields.DatetimeField(auto_now_add=True)
    updated_at = fields.DatetimeField(auto_now=True)

    tags = fields.ManyToManyField(
        tag_model_name,
        through="UserTagMap",
        forward_key="tag_id",
        backward_key="user_id",
    )

    followees: fields.ManyToManyRelation[User] = fields.ManyToManyField(  # noqa: F821
        user_model_name,
        through="Followee",
        related_name="followers",
        forward_key="followee_user_id",
        backward_key="user_id",
        description="关注者",
    )
    followers: fields.ManyToManyRelation[User]  # noqa: F821

    blocked_users: fields.ManyToManyRelation[
        User
    ] = fields.ManyToManyField(  # noqa: F821
        user_model_name,
        through="BlockList",
        related_name="blockers",
        forward_key="blocked_user_id",
        backward_key="user_id",
        description="黑名单",
    )
    blockers: fields.ManyToManyRelation[User]  # noqa: F821
    permissions = fields.ManyToManyField(
        permission_model_name,
        through="UserPermission",
        forward_key="permission_id",
        backward_key="user_id",
        description="权限列表",
    )

    liked_articles: fields.ManyToManyRelation[Article]  # noqa: F821
    liked_comments: fields.ManyToManyRelation[Comment]  # noqa: F821
    articles: fields.ForeignKeyRelation[Article]  # noqa: F821

    @classmethod
    async def get_user_by_name(cls, username: str) -> t.Optional[User]:
        user = await cls.get_or_none(username=username)
        return user

    @classmethod
    async def clear_in_cache(cls, key_template: str, **kwargs: t.Any):
        async with get_redis() as redis:
            cache_key = key_template.format(**kwargs)
            await redis.delete(cache_key)

    @GetOrSetInCache(BLOCK_ID_SET_KEY_TEMPLATE)
    async def get_blocked_user_ids(self, user_id: int) -> t.Set[str]:
        blocked_users = await self.blocked_users.all().only("auto_id")
        return {str(blocked_user.auto_id) for blocked_user in blocked_users}

    async def has_blocked(self, blocking_user_id: int) -> bool:
        return str(blocking_user_id) in await self.get_blocked_user_ids(
            user_id=self.auto_id
        )

    async def block_user_then_unfollow(self, user_id: int):
        user_block_list_len = await self.blocked_users.all().count()

        assert (
            user_block_list_len <= USER_BLOCK_LIST_MAX_LEN
        ), f"用户黑名单限制 {USER_BLOCK_LIST_MAX_LEN} 个"

        blocking_user = await User.get(auto_id=user_id)
        if not blocking_user:
            raise ApiError(Error.OBJECT_NOT_EXISTED)

        await self.blocked_users.add(blocking_user)
        await self.followees.remove(blocking_user)
        await self.clear_in_cache(FOLLOWEE_ID_SET_KEY_TEMPLATE, user_id=self.auto_id)
        await self.clear_in_cache(BLOCK_ID_SET_KEY_TEMPLATE, user_id=self.auto_id)

    async def unblock(self, user_id: int):
        blocking_user = await User.get(auto_id=user_id)

        if not blocking_user:
            raise ApiError(Error.OBJECT_NOT_EXISTED)

        await self.blocked_users.remove(blocking_user)
        await self.clear_in_cache(BLOCK_ID_SET_KEY_TEMPLATE, user_id=self.auto_id)

    @GetOrSetInCache(FOLLOWEE_ID_SET_KEY_TEMPLATE)
    async def get_followee_ids(self, user_id: int) -> t.Set[str]:
        followees = await self.followees.all().only("auto_id")
        return {str(followee.auto_id) for followee in followees}

    async def has_followed(self, user_id: int) -> bool:
        return str(user_id) in await self.get_followee_ids(user_id=self.auto_id)

    async def follow(self, user_id: int):
        user_followees_len = await self.followees.all().count()
        assert (
            user_followees_len <= USER_BLOCK_LIST_MAX_LEN
        ), f"用户关注限制 {USER_FOLLOWEE_MAX_LEN} 个"

        followee = await User.get(auto_id=user_id)
        if not followee:
            raise ApiError(Error.OBJECT_NOT_EXISTED)

        await self.followees.add(followee)
        await self.clear_in_cache(FOLLOWEE_ID_SET_KEY_TEMPLATE, user_id=self.auto_id)

    async def unfollow(self, user_id: int):
        followee = await User.get(auto_id=user_id)

        if not followee:
            raise ApiError(Error.OBJECT_NOT_EXISTED)

        await self.followees.remove(followee)
        await self.clear_in_cache(FOLLOWEE_ID_SET_KEY_TEMPLATE, user_id=self.auto_id)

    @GetOrSetInCache(USER_LIKED_ARTICLE_ID_SET_KEY_TEMPLATE)
    async def get_liked_article_ids(self, article_id: int) -> t.Set[str]:
        liked_articles = await self.liked_articles.all().only("auto_id")
        return {str(article.auto_id) for article in liked_articles}

    async def has_liked(self, article_id: int) -> bool:
        return str(article_id) in await self.get_liked_article_ids(
            article_id=article_id
        )

    async def like_article(self, article_id: int):
        from app.models import Article

        article = await Article.get(auto_id=article_id)

        if not article:
            raise ApiError(Error.OBJECT_NOT_EXISTED)

        await self.liked_articles.add(article)
        await self.clear_in_cache(
            USER_LIKED_ARTICLE_ID_SET_KEY_TEMPLATE, article_id=article_id
        )

    async def unlike_article(self, article_id: int):
        from app.models import Article

        article = await Article.get(auto_id=article_id)
        if not article:
            raise ApiError(Error.OBJECT_NOT_EXISTED)

        await self.liked_articles.remove(article)
        await self.clear_in_cache(
            USER_LIKED_ARTICLE_ID_SET_KEY_TEMPLATE, article_id=article_id
        )
